package c2.group.business.tb.ko;

import c2.advanced.business.base.KvData;
import c2.group.business.tb.TBGroupModule;
import cate.common.table.activity.throne.TBKoRoundRow;
import cate.common.table.d.GDFight;
import cate.common.table.d.GDFunc;
import cate.common.table.d.GDThrone;
import cate.common.util.TimeTool;
import cate.game.play.part.FightSide;
import cate.game.play.part.PlaySide;
import cate.game.play.proce.play.PlayPO;
import cate.game.play.vo.ReplayUtils;
import cate.game.pvp.throne.data.ko.*;
import cate.game.pvp.throne.data.trial.TBTrialRank;
import cate.game.pvp.throne.msg.TBKoBetNotice;
import cate.game.pvp.throne.msg.TBPartDataNotice;
import cate.game.role.RoleSimpleBase;
import cate.game.util.IntervalCounter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import cp.solution.manage.api.open.replay.ReplaySaveReq;
import cp.solution.util.collection.ListKit;
import easy.java.dev.note.NoteClass;
import easy.java.dev.note.NoteField;
import easy.java.practice.errorcode.EcResult;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.annotation.Transient;

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

@NoteClass("王座争霸淘汰赛模块")
@Slf4j
public class TBKoModule extends TBGroupModule<TBKoData> {

	@NoteField("基础数据")
	@Getter
	private TBKoStatus status;

	@NoteField("战斗任务")
	private LinkedList<TBKoFightTask> fightTasks = new LinkedList<>();

	@NoteField("竞猜数据发生变更")
	private boolean noticeBet;

	@Override
	protected TBKoData buildData() {
		return new TBKoData();
	}

	@Override
	public void readFromBD() {
		status = useDb().kv.getByKey(KvData.KEY_TB_KO_BASE, TBKoStatus.class);
		if (status == null) {
			status = new TBKoStatus();
		}
		status.init(KvData.KEY_TB_KO_BASE);
		for (TBKoData data : useDb().tb.getKo()) {
			data.init();
			groupMap.put(data.levelGroup, data);
		}
	}

	@Override
	public void save() {
		useDb().kv.save(status);
		useDb().tb.saveKo(new ArrayList<>(groupMap.values()));
	}

	@Override
	public void clear(){
		status.clear();
		forGroups(TBKoData::clear);
	}

	@Override
	public void onEnterNewSeason() {
		status.timeTable.onStart(context.table.throne.koRound, parent.getStatus().season.startTime);
	}

	@Override
	public void onPeriodChange(byte period) {
		if (period != GDThrone.Period.KO) {
			return;
		}
		for (Integer levelGroup : GDThrone.LevelGroup.ALL) {
			TBKoData koGroupData = getGroup(levelGroup);
			//根据选拔赛排行榜构建 分组赛的对阵信息
			List<TBTrialRank> list = new ArrayList<>(parent.trial.getGroup(levelGroup).rankList);
			//确保只有128个玩家进入淘汰赛
			list = ListKit.trimMaxKeepHead(list, GDThrone.TRIAL_RANK_MAX);
			//随机排序
			Collections.shuffle(list);
			int playerNum = list.size();
			log.info("[王座争霸]{}号等级分组有{}位玩家进入淘汰赛阶段", levelGroup, playerNum);
			int groupNum = Math.min(playerNum, GDThrone.TRIAL_RANK_MAX / 2);
			log.info("[王座争霸]{}号等级分组应该有{}场对阵数据", levelGroup, groupNum);
			for (int i = 1; i <= groupNum; i++) {
				TBKoMatch match = koGroupData.divide.getOrBuildMatch(i);
				int indexA = i - 1;
				int indexB = i - 1 + GDThrone.TRIAL_RANK_MAX / 2;
				match.a = list.get(indexA).owner.copy();
				if (playerNum > indexB) {
					match.b = list.get(indexB).owner.copy();
				}
			}
			log.info("王座争霸{}号等级分组最终生成了{}场对阵数据", levelGroup, koGroupData.divide.matchs.size());
		}
	}

	@Override
	public void onShutdown() throws Exception {
		doSettleFightTask(-1);
		super.onShutdown();
	}



	@Override
	public void onInterval() {
		if (parent.getStatus().timeTable.period != GDThrone.Period.KO) {
			return;
		}
		try {
			periodTick();
			taskTick();
			betTick();
		} catch (Exception e) {
			log.error("王座争霸淘汰赛tick error", e);
		}
	}


	@JsonIgnore
	@Transient
	private ReentrantLock betLock = new ReentrantLock();

	public TBKoBetNotice bet(byte team) {
		if (status.bet.cur == null) {
			return new TBKoBetNotice();
		}
		if (status.timeTable.inBet()) {
			try {
				betLock.lock();
				status.bet.cur.bet(team);
				noticeBet = true;
			} catch (Exception e) {
				log.error("", e);
			} finally {
				betLock.unlock();
			}
		}
		return TBKoBetNotice.of(status.bet.cur);
	}

	private void periodTick(){
		if (!fightTasks.isEmpty()) {
			return;
		}
		boolean change = status.timeTable.tick();
		if (!change) {
			return;
		}
		final int round = status.timeTable.getRound();
		final int period = status.timeTable.getPeriod();
		TBKoRoundRow row = useTable().throne.koRound.get(round);
		if (row == null) {
			return;
		}
		//模块特殊处理的逻辑
		switch (row.gameType) {
			case GDThrone.KoGameType.DIVIDE:
				handleDivide(row,period);
				break;
			case GDThrone.KoGameType.ADVANCE:
				handleAdvance(row,period);
				break;
			case GDThrone.KoGameType.CHAMPION:
				handleChampion(row,period);
				break;
			default:return;
		}
		//统一处理的逻辑
		switch (period) {
			case GDThrone.KoPeriod.BET:
				findBetMatch(row);
				break;
			case GDThrone.KoPeriod.FIGHT:

				break;
			case GDThrone.KoPeriod.REST:
				settleBet(row);
				status.bet.cur = null;
				break;
			default:break;
		}
		TBPartDataNotice notice = new TBPartDataNotice();
		forGroups(g -> notice.setKo(g.levelGroup, g));
		parent.message.noticePart(notice);
		parent.message.noticeKoBase(status);
	}

	private void taskTick() {
		doSettleFightTask(TimeTool.TimeCons.SECOND * 2);
	}


	private final IntervalCounter betTrigger = new IntervalCounter(5);

	private void betTick() {
		if (noticeBet && betTrigger.trigger()) {
			noticeBet = false;
			parent.message.noticeKoBet(status.bet.cur);
		}
	}


	/**
	 * 分组赛的处理
	 * @param roundRow
	 */
	private void handleDivide(TBKoRoundRow roundRow,int period) {
		switch (period){
			case GDThrone.KoPeriod.CAL:onDivideEnterCal(roundRow);break;
			case GDThrone.KoPeriod.REST:onDivideEnterRest();break;
			default:break;
		}
	}


	private void onDivideEnterCal(TBKoRoundRow roundRow) {
		for (TBKoData value : groupMap.values()) {
			for (TBKoMatch match : value.divide.matchs) {
				addFightTask(roundRow, match);
			}
		}
	}


	private void onDivideEnterRest() {
		for (TBKoData value : groupMap.values()) {
			List<TBKoRankItem> winners = new ArrayList<>();
			List<TBKoRankItem> losers = new ArrayList<>();
			//记录排行榜
			for (TBKoMatch match : value.divide.matchs) {
				RoleSimpleBase winnerInfo = match.getWinnerInfo();
				if(!winnerInfo.isEmpty()){
					TBKoRankItem winnerRank = new TBKoRankItem(winnerInfo);
					winners.add(winnerRank);
				}
				RoleSimpleBase loserInfo = match.getLoserInfo();
				if(!loserInfo.isEmpty()){
					TBKoRankItem winnerRank = new TBKoRankItem(loserInfo);
					losers.add(winnerRank);
				}
			}
			value.advance.buildGroup(GDThrone.KoGroupType.WINNER, winners);
			value.advance.buildGroup(GDThrone.KoGroupType.LOSER, losers);
		}
	}


	/**
	 * 晋级赛的处理
	 * @param roundRow
	 */
	private void handleAdvance(TBKoRoundRow roundRow, int period) {
		switch (period) {
			case GDThrone.KoPeriod.BET:
				onAdvanceEnterBet(roundRow);
				break;
			case GDThrone.KoPeriod.CAL:
				onAdvanceEnterCal(roundRow);
				break;
			case GDThrone.KoPeriod.REST:
				onAdvanceEnterRest(roundRow);
				break;
			default:
				break;
		}
	}

	/**
	 * 进入晋级赛竞猜阶段 进行分组匹配
	 * @param row
	 */
	private void onAdvanceEnterBet(TBKoRoundRow row) {
		final int round = row.round;
		//遍历等级分组
		for (TBKoData value : groupMap.values()) {
			//遍历胜败组
			for (TBKoAdvanceGroup advanceGroup : value.advance.groups.values()) {
				TBKoRound koRound = advanceGroup.getOrBuildRound(round);
				koRound.matchs.clear();
				for (int zone = 1; zone <= row.zoneNum; zone++) {
					for (int i = 1; i <= row.zoneGroupNum; i++) {
						int group = i + (zone - 1) * row.zoneGroupNum;
						TBKoMatch matchSPO = new TBKoMatch(group);
						matchSPO.initialize();
						koRound.matchs.add(matchSPO);
					}
				}
				switch (row.matchRule){
					case GDThrone.MatchRule.INIT_RULE: buildMatchByInitRule(row,advanceGroup,koRound);break;
					case GDThrone.MatchRule.LAST_ROUND: buildMatchByLastRound(advanceGroup,koRound);break;
					default:break;
				}
			}
		}
	}

	private void findBetMatch(TBKoRoundRow row) {
		final int round = row.round;
		final int gameType = row.gameType;
		for (int levelGroup = 3; levelGroup > 0; levelGroup--) {
			long powerDiffMin = -1;
			PlaySide betPsA = null;
			PlaySide betPsB = null;
			int matchGroup = -1;
			TBKoData koData = groupMap.get(levelGroup);
			List<TBKoMatch> matches = new ArrayList<>();
			switch (gameType) {
				case GDThrone.KoGameType.DIVIDE:
					matches.addAll(koData.divide.matchs);
					break;
				case GDThrone.KoGameType.ADVANCE:
					matches.addAll(koData.advance.groups.get(GDThrone.KoGroupType.WINNER).getOrBuildRound(round).matchs);
					break;
				case GDThrone.KoGameType.CHAMPION:
					matches.add(koData.champion.match);
					break;
				default:
					return;
			}
			for (TBKoMatch match : matches) {
				if (match == null) {
					continue;
				}
				if (match.canBet()) {
					PlaySide psA = parent.ps.getPlaySide(match.a.uid);
					if (psA == null) {
						log.error("[王座争霸]阵容数据丢失 玩家id={}", match.a.uid);
						continue;
					}
					PlaySide psB = parent.ps.getPlaySide(match.b.uid);
					if (psB == null) {
						log.error("[王座争霸]阵容数据丢失 玩家id={}", match.b.uid);
						continue;
					}
					long powerDiff = Math.abs(psA.snapshot.base.power - psB.snapshot.base.power);
					if (powerDiffMin < 0 || powerDiff < powerDiffMin) {
						powerDiffMin = powerDiff;
						betPsA = psA;
						betPsB = psB;
						matchGroup = match.matchGroup;
					}
				}
			}
			if (powerDiffMin >= 0) {
				TBKoCurBet curBet = new TBKoCurBet();
				curBet.gameType = gameType;
				curBet.levelGroup = levelGroup;
				curBet.round = round;
				curBet.matchGroup = matchGroup;
				curBet.a = betPsA;
				curBet.b = betPsB;
				curBet.calBothOdd();
				status.bet.cur = curBet;
			}
		}
	}

	private void settleBet(TBKoRoundRow roundRow) {
		TBKoCurBet curBet = status.bet.cur;
		if (curBet == null) {
			log.info("[王座争霸]尝试结算竞猜胜者但是并没有竞猜数据 round={}", roundRow.round);
			return;
		}
		TBKoMatch betMatch = getGroup(curBet.levelGroup).findBetMatch(curBet);
		if (betMatch == null) {
			log.info("[王座争霸]尝试结算竞猜胜者但是找不到对应的竞猜比赛");
			return;
		}
		TBKoBet.BetResult result = new TBKoBet.BetResult();
		result.winner = betMatch.winner;
		result.odd = betMatch.winner == GDFight.Team.A ? curBet.oddsA : curBet.oddsB;
		status.bet.addResult(roundRow.round, result);
	}


	/**
	 * 晋级赛第一轮的匹配
	 */
	private void buildMatchByInitRule(TBKoRoundRow row,TBKoAdvanceGroup advanceGroup,TBKoRound koRound){
		//赛区数量
		int zoneNum = row.zoneNum;
		//单个赛区的分组数量
		int zoneGroupNum = row.zoneGroupNum;
		List<TBKoRankItem> ranks = 	advanceGroup.rank.getRankList();
		//确保晋级赛第一轮只有64人参加
		ranks = ListKit.trimMaxKeepHead(ranks, GDThrone.TRIAL_RANK_MAX/2);
		Collections.shuffle(ranks);
		Map<Integer,List<TBKoRankItem>> zoneMap = new HashMap<>();
		//给玩家分配赛区
		for (int i = 0; i < ranks.size(); i++) {
			int zone = (i + 1) % zoneNum == 0 ? zoneNum : (i + 1) % zoneNum;
			List<TBKoRankItem> zonePlayers = zoneMap.computeIfAbsent(zone, k -> new ArrayList<>());
			zonePlayers.add(ranks.get(i));
		}
		//把玩家放入具体的位置
		for (Map.Entry<Integer, List<TBKoRankItem>> entry : zoneMap.entrySet()) {
			int zone = entry.getKey();
			List<TBKoRankItem> zonePlayers = entry.getValue();
			int startGroup = (zone - 1) * zoneGroupNum;
			for (int i = 0; i < zonePlayers.size(); i++) {
				//当前分组的第几个玩家
				int no = i + 1;
				TBKoRankItem item = zonePlayers.get(i);
				//根据玩家的顺序计算出他实际应该放的位置
				int putNo = calPutNo(no);
				//玩家应该进入的分组
				int matchGroup = calGroup(startGroup,putNo);
				TBKoMatch matchSPO = koRound.findMatch(matchGroup);
				if (matchSPO == null) {
					log.error("王座争霸晋级赛第一轮找不到匹配组 matchGroup = {}", matchGroup);
					continue;
				}
				//玩家在a方还是b方
				byte team = calMatchTeam(putNo);
				matchSPO.setPlayer(team,item.owner.copy());
			}
		}
	}

	private int calPutNo(int no) {
		int putNo;
		if (no <= GDThrone.MATCH_POS_ORDER.length) {
			putNo = GDThrone.MATCH_POS_ORDER[no - 1];
		} else {
			log.error("王座争霸淘汰赛单个赛区的人数超过配置no = {}", no);
			putNo = no;
		}
		return putNo;
	}


	private int calGroup(int startGroup, int putNo) {
		//当前玩家应该分在当前赛区的第几组
		int groupInZone = (int)Math.ceil((double)putNo/2.0);
		//在整个轮次中的分组
		return startGroup + groupInZone;
	}

	private byte calMatchTeam(int no) {
		return no % 2 == 1 ? GDFight.Team.A : GDFight.Team.B;
	}



	/**
	 * 依据上一轮的情况来分组
	 */
	private void buildMatchByLastRound(TBKoAdvanceGroup advanceGroup,TBKoRound koRound) {
		// 之前已经划分好赛区的话需要根据上一轮的分组情况来分组
		TBKoRound lastRound = advanceGroup.findRound(koRound.round - 1);
		for (TBKoMatch match : koRound.matchs) {
			//a方在上一轮的哪一个组
			int groupA = 2 * match.matchGroup - 1;
			//b方在上一轮的哪一个组
			int groupB = 2 * match.matchGroup;
			TBKoMatch matchGroupA = lastRound.findMatch(groupA);
			if (matchGroupA != null && !matchGroupA.isEmpty()) {
				match.a = matchGroupA.getWinnerInfo().copy();
			}
			TBKoMatch matchGroupB = lastRound.findMatch(groupB);
			if (matchGroupB != null && !matchGroupB.isEmpty()) {
				match.b = matchGroupB.getWinnerInfo().copy();
			}
		}
	}


	/**
	 * 进入晋级赛战斗计算阶段
	 * @param roundRow
	 */
	private void onAdvanceEnterCal(TBKoRoundRow roundRow) {
		for (TBKoData value : groupMap.values()) {
			//遍历胜败组
			for (TBKoAdvanceGroup advanceGroup : value.advance.groups.values()) {
				for (TBKoMatch match : advanceGroup.findRound(roundRow.round).matchs) {
					addFightTask(roundRow, match);
				}
			}
		}
	}


	/**
	 * 进入晋级赛战斗阶段 更新排行榜
	 */
	private void onAdvanceEnterRest(TBKoRoundRow roundRow) {
		//遍历等级分组
		for (TBKoData value : groupMap.values()) {
			//遍历胜败组
			for (TBKoAdvanceGroup advanceGroup : value.advance.groups.values()) {
				TBKoRound roundSPO = advanceGroup.findRound(roundRow.round);
				for (TBKoMatch match : roundSPO.matchs) {
					if (match.isEmpty()) {
						continue;
					}
					RoleSimpleBase winner = match.getWinnerInfo();
					if (!winner.isEmpty()) {
						TBKoRankItem winnerRank = advanceGroup.rank.findByUid(winner.uid);
						winnerRank.onWin();
						winnerRank.owner = parent.ps.getPlaySide(winner.uid).snapshot.base.copy();
					}
					RoleSimpleBase loser = match.getLoserInfo();
					if (!loser.isEmpty()) {
						TBKoRankItem loserRank = advanceGroup.rank.findByUid(loser.uid);
						loserRank.onLose();
						loserRank.owner = parent.ps.getPlaySide(loser.uid).snapshot.base.copy();
					}
				}
				//排序
				advanceGroup.rank.sort();
			}
		}
	}


	/**
	 * 冠军赛的处理
	 * @param roundRow
	 */
	private void handleChampion(TBKoRoundRow roundRow,int period){
		switch (period){
			case GDThrone.KoPeriod.BET:onChampionEnterBet();break;
			case GDThrone.KoPeriod.CAL: onChampionEnterCal(roundRow);break;
			case GDThrone.KoPeriod.REST: onChampionEnterRest();break;
			default:break;
		}
	}

	private void onChampionEnterBet() {
		//遍历等级分组
		for (TBKoData value : groupMap.values()) {
			TBKoRankItem winnerChampion = value.advance.getGroup(GDThrone.KoGroupType.WINNER).rank.getChampion();
			TBKoRankItem loserChampion = value.advance.getGroup(GDThrone.KoGroupType.LOSER).rank.getChampion();
			if (winnerChampion != null) {
				value.champion.match.setPlayer(GDFight.Team.A, winnerChampion.owner.copy());
			}
			if (loserChampion != null) {
				value.champion.match.setPlayer(GDFight.Team.B, loserChampion.owner.copy());
			}
		}
	}

	private void onChampionEnterCal(TBKoRoundRow roundRow) {
		for (TBKoData value : groupMap.values()) {
			addFightTask(roundRow, value.champion.match);
		}
	}

	private void onChampionEnterRest(){
		for (TBKoData value : groupMap.values()) {
			status.setChampion(parent.getStatus().season.index, value.levelGroup, value.champion.match);
		}
	}

	private void addFightTask(TBKoRoundRow roundRow,TBKoMatch match){
		fightTasks.add(new TBKoFightTask(roundRow,match));
	}

	private void doSettleFightTask(long maxTime) {
		if (fightTasks.isEmpty()) {
			return;
		}
		final long startTime = System.currentTimeMillis();
		long pastTime = 0L;
		int settleNum = 0;
		TBKoFightTask task;
		while ((task = fightTasks.poll()) != null) {
			handleMatchFight(task.row, task.match);
			settleNum++;
			pastTime = System.currentTimeMillis() - startTime;
			if (maxTime > 0 && pastTime > maxTime) {
				break;
			}
		}
		log.info("[王座争霸]淘汰赛进行了一次战斗结算 共结算了{}场战斗 耗时{}ms：", settleNum, pastTime);
	}

	/**
	 *
	 * @param roundRow  淘汰赛第几轮次
	 * @param match
	 */
	private void handleMatchFight(TBKoRoundRow roundRow, TBKoMatch match) {
		if (match.isEmpty()) {
			return;
		}
		//a方没人直接获胜
		if (match.a.isEmpty()) {
			match.winner = GDFight.Team.B;
			return;
		}
		//b方没人直接获胜
		if (match.b.isEmpty()) {
			match.winner = GDFight.Team.A;
			return;
		}
		PlaySide psA = parent.ps.getPlaySide(match.a.uid);
		PlaySide psB = parent.ps.getPlaySide(match.b.uid);

		if (psA == null) {
			log.error("[王座争霸]战斗a方战斗阵容丢失");
			match.winner = GDFight.Team.B;
			return;
		}
		if (psB == null) {
			log.error("[王座争霸]战斗b方战斗阵容丢失");
			match.winner = GDFight.Team.A;
			return;
		}
		FightSide.build(context.play.getPlayBuildContext(), GDFight.Team.A, psA);
		FightSide.build(context.play.getPlayBuildContext(), GDFight.Team.B, psB);

		//3局2胜的战斗
		final int matchRoundMax = roundRow.bestOfNum;

		int matchRound = 0;
		while (matchRound < matchRoundMax) {
			matchRound++;
			TBFightContext ftc = new TBFightContext();
			ftc.funcId = GDFunc.THRONE_KO;
			ftc.round = roundRow.round;
			ftc.winTimesA = match.getWinTime(GDFight.Team.A);
			ftc.winTimesB = match.getWinTime(GDFight.Team.B);
			FightSide sideA = FightSide.build(usePlay().getPlayBuildContext(), GDFight.Team.A, psA);
			FightSide sideB = FightSide.build(usePlay().getPlayBuildContext(), GDFight.Team.B, psB);
			EcResult<PlayPO> r = start(ftc, sideA, sideB);
			if (!r.ok()) {
				match.winner = GDFight.Team.B;
				return;
			}
			PlayPO play = r.data;
			long replayUid = 0L;
			//保存录像
			try {
				ReplaySaveReq saveReq = ReplayUtils.toReplaySaveReq(play, context.time.now() + GDThrone.REPLAY_SAVE_DAY * TimeTool.TimeCons.DAY);
				replayUid = context.api.openApi().saveReplay(saveReq).data.getId();
			} catch (Exception e) {
				log.error("", e);
			}
			match.addReplayInfo(replayUid, play);
			match.calWinner(roundRow.winTime);
			if (match.isOver()) {
				return;
			}
		}
	}

	/** 开启一场战斗 */
	private EcResult<PlayPO> start(TBFightContext fightContext, FightSide sideA, FightSide sideB) {
		EcResult<PlayPO> r = new EcResult();
		try {
			r = context.fightHandler.start(new TBKoFTC(), sideA, sideB);
		} catch (Exception e) {
			log.error("[王座争霸]战斗发生异常", e);
			return r.fail("[王座争霸]战斗发生异常");
		}
		PlayPO play = r.data;
		play.story.params.put("round", fightContext.round);
		play.story.params.put("winTimeA", fightContext.winTimesA);
		play.story.params.put("winTimeB", fightContext.winTimesB);
		r.data = play;
		return r.success();
	}
}
